﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Smartee.Modelling;
using Mls.IO;
using System.IO;
using System.Diagnostics;
using Mls.Interpreters;
using Smartee.RichText.VisitorAndBuilder;
using System.Threading;

namespace Mls
{
    public partial class MainForm : Form
    {
        private const string DocumentsPath = @"..\..\..\..\_MlsApplication\disk\doc";

        public MainForm()
        {
            InitializeComponent();
        }

        private class DocEntry
        {
            public enum ErrorLevel
            {
                None,
                ErrorReadingBinray,
                ErrorGeneratingRawRTF,
                FatalGeneratingRawRTF,
                ErrorGeneratingTarget,
                FatalGeneratingTarget
            };

            public Document Document;
            public string RawRtf;
            public string TargetRtf;
            public RawFileParser RawParser;
            public Parser TargetParser;

            public Exception BinaryReadError;
            public Exception RawReadError;
            public Exception TargetReadError;

            public ErrorLevel GetErrorLevel()
            {
                if (TargetReadError != null)
                    return ErrorLevel.FatalGeneratingTarget;
                //if (TargetParser.Errors)
                    //return ErrorLevel.ErrorGeneratingTarget;
                if (RawReadError != null)
                    return ErrorLevel.FatalGeneratingRawRTF;
                if (RawParser != null && RawParser.Errors.Count > 0)
                    return ErrorLevel.ErrorGeneratingRawRTF;
                if (BinaryReadError != null)
                    return ErrorLevel.ErrorReadingBinray;
                return ErrorLevel.None;
            }

            public string NodeTitle
            {
                get
                {
                    string title = Document.Name;
                    ErrorLevel error = GetErrorLevel();
                    if (error > ErrorLevel.None)
                        title += " [" + error.ToString() + "]";
                    return title;
                }
            }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            trvChains.Nodes.Clear();
            lstChainsErrors.Items.Clear();
            lstFileErrors.Items.Clear();

            Enabled = false;
            new Thread(new ThreadStart(LoadAll)).Start();
        }

        private delegate TreeNode AddNewNodeDelegate();
        private TreeNode AddNewNode()
        {
            return trvChains.Nodes.Add("Chain");
        }

        private delegate TreeNode AddNewSubNodeDelegate(TreeNode parent);
        private TreeNode AddNewSubNode(TreeNode parent)
        {
            TreeNode newNode = parent.Nodes.Add("");
            parent.Expand();
            return newNode;
        }

        private delegate void AddChainErrorDelegate(string message);
        private void AddChainError(string message)
        {
            lstChainsErrors.Items.Add(message);
        }

        private delegate void SetNodeNameDelegate(TreeNode node, string name);
        private void SetNodeName(TreeNode node, string name)
        {
            node.Text = name;
        }

        private delegate void EndLoadingAllDelegate();
        private void EndLoadingAll()
        {
            trvChains.ExpandAll();
            Enabled = true;
        }

        private void LoadAll()
        {
            string[] errors;
            DocumentsChain[] chains = DirectoryChainFinder.Find(DocumentsPath, out errors, Language.LanguageSettings.Default);

            foreach (string error in errors)
                Invoke(new AddChainErrorDelegate(AddChainError), error);

            foreach (DocumentsChain chain in chains)
            {
                TreeNode chainNode = Invoke(new AddNewNodeDelegate(AddNewNode)) as TreeNode;
                chainNode.Tag = chain;
                foreach (Document doc in chain.Documents)
                {
                    DocEntry entry = new DocEntry();
                    entry.Document = doc;

                    TreeNode docNode = Invoke(new AddNewSubNodeDelegate(AddNewSubNode), chainNode) as TreeNode;
                    docNode.Tag = entry;
                    ReadDocument(entry, docNode);

                    Invoke(new SetNodeNameDelegate(SetNodeName), docNode, entry.NodeTitle);
                }
            }

            Invoke(new EndLoadingAllDelegate(EndLoadingAll));
        }

        private void ReadDocument(TreeNode docNode)
        {
            ReadDocument(docNode.Tag as DocEntry, docNode);
        }

        private void ReadDocument(DocEntry entry, TreeNode docNode)
        {
            entry.RawReadError = null;

            ReadDocumentBinary(entry, docNode);

            if (entry.GetErrorLevel() == DocEntry.ErrorLevel.None)
                ReadDocumentRaw(entry, docNode);

            if (entry.GetErrorLevel() == DocEntry.ErrorLevel.None)
                ReadDocumentTarget(entry, docNode);
        }

        private void ReadDocumentBinary(DocEntry entry, TreeNode docNode)
        {
            try
            {
                entry.BinaryReadError = null;
                entry.Document = new Document(entry.Document.Filename, entry.Document.LanguageSettings);
            }
            catch (Exception exception)
            {
                entry.BinaryReadError = exception;
            }
        }

        private void ReadDocumentRaw(DocEntry entry, TreeNode docNode)
        {
            try
            {
                entry.RawReadError = null;
                entry.RawParser = new RawFileParser();
                entry.RawParser.ReadFile(entry.Document, out entry.RawRtf);
            }
            catch (Exception exception)
            {
                entry.RawReadError = exception;
            }
        }

        private RichTextInterpreter CreateRTFInterpreter(Builder builder)
        {
            RichTextInterpreter interpreter = new RichTextInterpreter(builder);

            interpreter.AddFont(Language.Type.Arabic, "swiss", "Tahoma");
            interpreter.AddFont(Language.Type.Arabic, "swiss", "Tahoma");
            interpreter.AddFont(Language.Type.Arabic, "swiss", "Tahoma");
            interpreter.AddFont(Language.Type.Arabic, "swiss", "Tahoma");
            interpreter.AddFont(Language.Type.Latin, "swiss", "Arial");
            interpreter.AddFont(Language.Type.Coptic, "swiss", "ArialCoptic");
            interpreter.AddFont(Language.Type.Coptic, "swiss", "ArialCoptic");
            interpreter.AddFont(Language.Type.Coptic, "swiss", "ArialCoptic");
            interpreter.AddFont(Language.Type.Coptic, "swiss", "ArialCoptic");
            interpreter.AddFont(Language.Type.Coptic, "swiss", "ArialCoptic");
            interpreter.AddFont(Language.Type.Greek, "swiss", "Arial");

            return interpreter;
        }

        private void ReadDocumentTarget(DocEntry entry, TreeNode docNode)
        {
            try
            {
                entry.TargetReadError = null;

                Builder builder = new Builder();
                entry.TargetParser = new Parser(CreateRTFInterpreter(builder));
                entry.TargetParser.Parse(entry.Document);
                StringWriter writer = new StringWriter();
                builder.Document.Accept(new Smartee.RichText.VisitorAndBuilder.RtfWriter(writer));
                entry.TargetRtf = writer.ToString();
            }
            catch (Exception exception)
            {
                entry.TargetReadError = exception;
            }
        }

        private void trvChains_AfterSelect(object sender, TreeViewEventArgs e)
        {
            lstFileErrors.Items.Clear();
            rtxRaw.Rtf = string.Empty;
            pnlRawEdit.Enabled = false;

            if (trvChains.SelectedNode.Tag is DocEntry)
            {
                foreach (RawFileParser.Error error in (trvChains.SelectedNode.Tag as DocEntry).RawParser.Errors)
                    lstFileErrors.Items.Add(error);

                rtxRaw.Rtf = (trvChains.SelectedNode.Tag as DocEntry).RawRtf;
                pnlRawEdit.Enabled = true;

                rtxTarget.Rtf = (trvChains.SelectedNode.Tag as DocEntry).TargetRtf;
                rtxTarget.Select(0, 0);
                rtxRaw.ScrollToCaret();
            }
        }

        private void rtxRaw_SelectionChanged(object sender, EventArgs e)
        {
            if (trvChains.SelectedNode.Tag is DocEntry)
            {
                RawFileParser.Glyph glyph = (trvChains.SelectedNode.Tag as DocEntry).RawParser.FindGlyph(rtxRaw.SelectionStart);
                txtSource.Text = string.Format("Position: {0} -> 0x{1:X2} - {2}  Accents: {3}", glyph.FilePosition, glyph.OriginalCharacter.Raw,
                    glyph.OriginalCharacter.Language.LanguageType, glyph.OriginalCharacter.IsAccented ?
                    string.Join(" ,", (from a in glyph.OriginalCharacter.Accents.AccentIds select string.Format("0x{0:X2}", a)).ToArray()) : "N/A");
                txtTarget.Text = string.Format("Position: {0} -> '{1}' - {2}", rtxRaw.SelectionStart, glyph.Text,
                    string.Join(", ", (from a in glyph.Text select string.Format("0x{0:X4}", (ushort)a)).ToArray()));
            }
        }

        private void btnNewLine_Click(object sender, EventArgs e)
        {
            InsertCharacter((trvChains.SelectedNode.Tag as DocEntry).RawParser.FindGlyph(rtxRaw.SelectionStart).FilePosition, Character.NewLine);
        }

        private void btnEmbed_Click(object sender, EventArgs e)
        {
            InsertCharacter((trvChains.SelectedNode.Tag as DocEntry).RawParser.FindGlyph(rtxRaw.SelectionStart).FilePosition, Character.Embed);
        }

        private void btnDelimter_Click(object sender, EventArgs e)
        {
            InsertCharacter((trvChains.SelectedNode.Tag as DocEntry).RawParser.FindGlyph(rtxRaw.SelectionStart).FilePosition, Character.Delimeter);
        }

        private void btnDelete_Click(object sender, EventArgs e)
        {
            InsertCharacter((trvChains.SelectedNode.Tag as DocEntry).RawParser.FindGlyph(rtxRaw.SelectionStart).FilePosition, null);
        }

        private void btnInsert_Click(object sender, EventArgs e)
        {

        }

        private void lstFileErrors_SelectedIndexChanged(object sender, EventArgs e)
        {
            rtxRaw.SelectionStart = ((RawFileParser.Error)lstFileErrors.SelectedItem).ShowingPosition;
            rtxRaw.SelectionLength = ((RawFileParser.Error)lstFileErrors.SelectedItem).ShowingLength;
            rtxRaw.ScrollToCaret();
        }

        private void InsertCharacter(int position, Character character)
        {
            DocEntry selectedEntry = trvChains.SelectedNode.Tag as DocEntry;

            string filename = Path.Combine(DocumentsPath, selectedEntry.Document.Name);

            string backedFilename = filename + ".BAK";

            for (int i = 1; i < int.MaxValue && File.Exists(backedFilename); i++)
                backedFilename = filename + ".BAK" + i.ToString();

            File.Move(filename, backedFilename);

            BinaryReader reader = new BinaryReader(File.OpenRead(backedFilename));
            byte[] header = new byte[0x190];
            if (reader.Read(header, 0, header.Length) != header.Length)
                throw new InvalidProgramException();

            BinaryWriter writer = new BinaryWriter(File.OpenWrite(filename));
            writer.Write(header);

            int filePosition = 0;

            while (true)
            {
                try
                {
                    if (filePosition == position && character != null)
                    {
                        ushort[] data = character.Encoded;
                        foreach (ushort word in data)
                            writer.Write(word);
                    }

                    ushort firstWord = reader.ReadUInt16();
                    if (filePosition != position || character != null)
                        writer.Write(firstWord);

                    if (Character.IsFollowedByAccents(firstWord))
                        if (filePosition != position || character != null)
                            writer.Write(reader.ReadUInt16());
                        else
                            reader.ReadUInt16();

                    filePosition++;
                }
                catch (EndOfStreamException)
                {
                    break;
                }
            }

            reader.Close();
            writer.Close();

            ReadDocument(selectedEntry, trvChains.SelectedNode);
            trvChains_AfterSelect(this, new TreeViewEventArgs(trvChains.SelectedNode));
        }

        private void btnShowRawInWord_Click(object sender, EventArgs e)
        {
            string tempPath = Path.GetTempPath();
            string filename = "Mls";
            for (int i = 0; i < int.MaxValue && File.Exists(filename + ".rtf"); i++)
                filename = "Mls" + i.ToString();

            filename = Path.Combine(tempPath, filename + ".rtf");

            File.WriteAllText(filename, (trvChains.SelectedNode.Tag as DocEntry).RawRtf);

            Process.Start(filename);
        }

        private void btnBackup_Click(object sender, EventArgs e)
        {
            string filename = Path.Combine(DocumentsPath, (trvChains.SelectedNode.Tag as DocEntry).Document.Name);

            string backedFilename = filename + ".BAK";

            for (int i = 1; i < int.MaxValue && File.Exists(backedFilename); i++)
                backedFilename = filename + ".BAK" + i.ToString();

            File.Move(filename, backedFilename);

            TreeNode chainNode = trvChains.SelectedNode.Parent;
            chainNode.Nodes.Remove(trvChains.SelectedNode);
        }

        private void btnShowTargetInWord_Click(object sender, EventArgs e)
        {
            string tempPath = Path.GetTempPath();
            string filename = "Mls";
            for (int i = 0; i < int.MaxValue && File.Exists(filename + ".rtf"); i++)
                filename = "Mls" + i.ToString();

            filename = Path.Combine(tempPath, filename + ".rtf");

            File.WriteAllText(filename, (trvChains.SelectedNode.Tag as DocEntry).TargetRtf);

            Process.Start(filename);
        }

        private void btnReloadAll_Click(object sender, EventArgs e)
        {

        }

        private void btnMakeFirst_Click(object sender, EventArgs e)
        {

        }

        private void btnChainToTarget_Click(object sender, EventArgs e)
        {
            DocumentsChain chain = trvChains.SelectedNode.Tag is DocumentsChain ? trvChains.SelectedNode.Tag as DocumentsChain :
                trvChains.SelectedNode.Parent.Tag as DocumentsChain;

            if (chain != null)
            {
                Builder builder = new Builder();
                new Parser(CreateRTFInterpreter(builder)).Parse(chain);

                StringWriter writer = new StringWriter();
                builder.Document.Accept(new Smartee.RichText.VisitorAndBuilder.RtfWriter(writer));

                string tempPath = Path.GetTempPath();
                string filename = "Mls";
                for (int i = 0; i < int.MaxValue && File.Exists(filename + ".rtf"); i++)
                    filename = "Mls" + i.ToString();

                filename = Path.Combine(tempPath, filename + ".rtf");

                File.WriteAllText(filename, writer.ToString());

                Process.Start(filename);
            }
        }

        private void btnChoosePrev_Click(object sender, EventArgs e)
        {

        }

        private void btnChooseNext_Click(object sender, EventArgs e)
        {

        }

        private void btnRefresh_Click(object sender, EventArgs e)
        {
            ReadDocument(trvChains.SelectedNode.Tag as DocEntry, trvChains.SelectedNode);
            trvChains_AfterSelect(this, new TreeViewEventArgs(trvChains.SelectedNode));
        }
    }
}
